package net.towee.model.account.impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import net.towee.model.Settings;
import net.towee.model.account.AccountData;
import net.towee.model.account.AccountGroup;
import net.towee.model.account.AccountGroupsContainer;
import net.towee.model.account.AccountIdentity;
import net.towee.model.account.AccountProfile;
import net.towee.model.account.Board;
import net.towee.model.account.BoardsContainer;
import net.towee.model.account.ContactsContainer;
import net.towee.model.account.ContactsGroup;
import net.towee.model.account.Gadget;
import net.towee.model.account.LoginzaAccount;
import net.towee.model.account.LoginzaAccountsContainer;
import net.towee.model.account.Role;
import net.towee.model.group.GroupIdentity;
import net.towee.model.group.impl.GroupIdentityImpl;
import net.towee.model.impl.SettingsImpl;
import net.towee.model.utils.Pair;
import net.towee.server.persistence.impl.MongoDbStore;

import com.google.code.morphia.annotations.Entity;
import com.google.code.morphia.annotations.Id;
import com.google.code.morphia.annotations.Indexed;
import com.google.code.morphia.annotations.PostLoad;
import com.google.code.morphia.annotations.Reference;
import com.google.code.morphia.annotations.Transient;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.user.client.Window;

/**
 * Implementation of account data
 * @author c58
 */
@Entity(MongoDbStore.ACCOUNT_COLLECTION)
public class AccountDataImpl extends SettingsImpl implements AccountData, LoginzaAccountsContainer,
		AccountGroupsContainer, Settings, BoardsContainer, ContactsContainer {
	@Id private String id;
	
	private String cookieId;
	
	// List of boards
	private List<Board> boards;
	
	// Account groups
	@Indexed private List<AccountGroup> accountGroups;
	
	// Loginza accounts container
	@Indexed private List<LoginzaAccount> loginzaAccounts;
	
	// Referenced identity of account
	@Reference private AccountIdentity identity;
	
	// Referenced profile of account
	@Reference private AccountProfile accountProfile;
	
	// Contacts of this user
	@Reference private List<ContactsGroup> contacts;
	
	// Password of the user
	transient private byte[] digest;
	transient private byte[] salt;
	
	// List of attached emails
	@Indexed private List<String> emails;
	
	// New user indicator
	@Transient private boolean isNew;
	
	// Date of registration
	private Date regDate;
	
	// Date of last session
	private Date lastSession;
	
	// Empty constructor for morphia
	public AccountDataImpl(){}
	public AccountDataImpl(AccountIdentity identity){
		this.identity = identity;
		this.id = identity.getId();
		
		this.loginzaAccounts = new ArrayList<LoginzaAccount>();
		this.accountGroups = new ArrayList<AccountGroup>();
		this.accountProfile = new AccountProfileImpl(identity);
		this.settings = new HashMap<String, String>();
		this.boards = new ArrayList<Board>();
		this.contacts = new ArrayList<ContactsGroup>();
		this.emails = new ArrayList<String>();
		
		this.regDate = new Date();
		this.lastSession = new Date();
	}
	
	@PostLoad
	private void createEmptyArrays() {
		if(loginzaAccounts == null)
			loginzaAccounts = new ArrayList<LoginzaAccount>();
		if(accountGroups == null)
			accountGroups = new ArrayList<AccountGroup>();
		if(settings == null)
			settings = new HashMap<String, String>();
		if(boards == null)
			boards = new ArrayList<Board>();
		if(contacts == null)
			contacts = new ArrayList<ContactsGroup>();
		if(emails == null)
			emails = new ArrayList<String>();
	}

	@Override
	public String getId() {
		return id;
	}
	
	/*
	 * Account groups
	 */
	@Override
	public AccountGroupsContainer getGroups() {
		return this;
	}
	@Override
	public boolean isInGroups(String... groupIds) {
		for(String g : groupIds) {
			if(!accountGroups.contains(new GroupIdentityImpl(g)))
				return false;
		}
				
		return true;
	}
	@Override
	public AccountGroup getAccountGroup(String groupId) {
		if(accountGroups.contains( new GroupIdentityImpl(groupId) ))
			return accountGroups.get( accountGroups.indexOf( new GroupIdentityImpl(groupId) ) );
		
		return null;
	}
	@Override
	public AccountGroup enterToGroup(GroupIdentity groupId, Role role) {
		AccountGroup accg = new AccountGroupImpl(groupId, role);
		accountGroups.add(accg);
		return accg;
	}
	@Override
	public void removeFromGroup(String groupId) {
		accountGroups.remove( new GroupIdentityImpl(groupId) );
	}
	@Override
	public List<AccountGroup> getGroupsList() {
		return accountGroups;
	}
	
	/*
	 * LOGINZA account
	 */
	@Override
	public LoginzaAccountsContainer getAttachedAccounts() {
		return this;
	}
	@Override
	public LoginzaAccount getAccount(int index) {
		return loginzaAccounts.get(index);
	}
	@Override
	public int getAccountIndex(LoginzaAccount account) {
		return loginzaAccounts.indexOf(account);
	}
	@Override
	public void addAccount(LoginzaAccount account) {
		loginzaAccounts.add(account);
	}
	@Override
	public LoginzaAccount addAccount(String identity, String provider) {
		LoginzaAccount acc = new LoginzaAccountImpl(identity, provider);
		addAccount(acc);
		return acc;
	}
	@Override
	public void removeAccount(int index) {
		loginzaAccounts.remove(index);
	}
	@Override
	public void removeAccount(LoginzaAccount provider) {
		loginzaAccounts.remove(provider);
	}
	@Override
	public List<LoginzaAccount> getAccountsList() {
		return loginzaAccounts;
	}

	/*
	 * Account identity
	 */
	@Override
	public AccountIdentity getAccountIdentity() {
		return identity;
	}
	
	/*
	 * Account settings 
	 */
	@Override
	public Settings getSettingsContainer() {
		return this;
	}

	/*
	 * Account profile
	 */
	@Override
	public AccountProfile getProfile() {
		return this.accountProfile;
	}
	
	/*
	 * Boards
	 */
	@Override
	public BoardsContainer getBoardsContainer() {
		return this;
	}
	@Override
	public void addBoard(Board board) {
		boards.add(board);
	}
	@Override
	public void removeBoard(String id) {
		boards.remove(new BoardImpl(id));
	}
	@Override
	public void removeBoard(Board board) {
		boards.remove(board);
	}
	@Override
	public Board getBoard(String id) {
		int index = boards.indexOf(new BoardImpl(id));
		
		if(index >= 0)
			return boards.get(index);
		
		return null;
	}
	@Override
	public Board getBoard(int index) {
		if(index < boards.size())
			return boards.get(index);
		
		return null;
	}
	@Override
	public int getBoardIndex(Board board) {
		return boards.indexOf(board);
	}
	@Override
	public boolean hasBoard(Board board) {
		return boards.contains(board);
	}
	@Override
	public List<Gadget> getMinimizedGadgets() {
		List<Gadget> gadgets = new ArrayList<Gadget>();
		
		for(Board b : boards) {
			for(Gadget g : b.getGadgets()) {
				if(g.isMinimized()) {
					g.putSetting("boardIndex", boards.indexOf(b)+"");
					gadgets.add(g);
				}
			}
		}
		
		return gadgets;
	}
	@Override
	public List<Board> getBoardsList() {
		return boards;
	}
	
	/*
	 * Contacts
	 */
	@Override
	public ContactsContainer getContacts() {
		return this;
	}
	@Override
	public List<AccountIdentity> getAllContacts() {
		List<AccountIdentity> cs = new ArrayList<AccountIdentity>();
		for(ContactsGroup g : contacts) {
			cs.addAll(g.getContactsList());
		}
		
		return cs;
	}
	@Override
	public List<ContactsGroup> getContactsGroupsList() {
		return contacts;
	}
	@Override
	public ContactsGroup getContactsGroup(String id) {
		int index = contacts.indexOf(new ContactsGroupImpl(id));
		
		if(index < 0)
			return null;
		
		return contacts.get( contacts.indexOf(new ContactsGroupImpl(id)) );
	}
	@Override
	public void addContactsGroup(ContactsGroup group) {
		contacts.add(group);
	}
	@Override
	public void removeContactsGroup(String id) {
		contacts.remove(new ContactsGroupImpl(id));
	}
	@Override
	public void removeContactsGroup(ContactsGroup group) {
		contacts.remove(group);
	}

	/*
	 * Password
	 */
	@Override
	public Pair<byte[], byte[]> getSaltDigest() {
		return new Pair<byte[], byte[]>(this.salt, this.digest);
	}

	@Override
	public void setSaltDigest(byte[] salt, byte[] digest) {
		this.salt = salt;
		this.digest = digest;
	}

	/*
	 * Emails
	 */
	@Override
	public List<String> getEmails() {
		return emails;
	}
	@Override
	public boolean addEmail(String email) {
		if(RegExp.compile("^[a-z0-9]+(\\.[_a-z0-9]+)*@[a-z0-9]+(\\.[-a-z0-9]+)*\\.(com|net|org|ru)$").test(email)) {
			emails.add(email);
			return true;
		}
		
		return false;
	}
	@Override
	public void removeEmail(String email) {
		emails.remove(email);
	}
	
	/*
	 * OTHER
	 */
	@Override
	public Date getRegDate() {
		return regDate;
	}

	@Override
	public Date getLastSessionDate() {
		return lastSession;
	}

	@Override
	public boolean isNew() {
		return isNew;
	}

	@Override
	public void setNew(boolean newUser) {
		this.isNew = newUser;
	}
	@Override
	public String getCookieId() {
		return cookieId;
	}
	@Override
	public void setCookieId(String id) {
		this.cookieId = id;
	}
}
